﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using Conditional = System.Diagnostics.ConditionalAttribute;

public class MuYuPipeline : RenderPipeline
{
    private CullResults cull;

    private const string cameraBufferName = "MuYuCamera";
    private CommandBuffer cameraBuffer = new CommandBuffer()
    {
        name = cameraBufferName
    };

    private const string shadowBufferName = "RenderShadow";
    private CommandBuffer shadowBuffer = new CommandBuffer()
    {
        name = shadowBufferName
    };

    private Material errorMaterial;

    private DrawRendererFlags drawFlags;

    private RenderTexture shadowMap;
    private int shadowMapSize;

    #region Light
    const int maxVisibleLights = 16;
    static int shadowCastingLightIndex = 1;
    static int visibleColorsID = Shader.PropertyToID("_VisibleLightColors");
    static int visibleDirectionsOrPositionsID = Shader.PropertyToID("_VisibleLightDirectionsOrPositions");
    static int visibleLightAttenuationsID = Shader.PropertyToID("_VisibleLightAttenuations");
    static int visibleSpotLightDirectionID = Shader.PropertyToID("_VisibleSpotLightDirection");
    static int lightIndicesOffsetAndCount = Shader.PropertyToID("unity_LightIndicesOffsetAndCount");
    static int shadowMapID = Shader.PropertyToID("_ShadowMap");
    static int worldToShadowMatrixID = Shader.PropertyToID("_WorldToShadowMatrix");
    static int shadowBiasID = Shader.PropertyToID("_ShadowBias");
    static int shadowStrengthID = Shader.PropertyToID("_ShadowStrength");
    Vector4[] visibleLightColors = new Vector4[maxVisibleLights];
    Vector4[] visibleLightDirectionOrPositions = new Vector4[maxVisibleLights];
    Vector4[] visibleLightAttenuations = new Vector4[maxVisibleLights];
    Vector4[] visibleSpotLightDirection = new Vector4[maxVisibleLights];
    #endregion

    public MuYuPipeline(bool dynamicBatching, bool instancingBatching, int shadowMapSize)
    {
        //Linear Space
        GraphicsSettings.lightsUseLinearIntensity = true;
        if (dynamicBatching)
            drawFlags = DrawRendererFlags.EnableDynamicBatching;
        if (instancingBatching)
            drawFlags |= DrawRendererFlags.EnableInstancing;
        this.shadowMapSize = shadowMapSize;
    }

    public override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
    {
        base.Render(renderContext, cameras);
        foreach (var camera in cameras)
            Render(renderContext, camera);
    }

    private void Render(ScriptableRenderContext context, Camera camera)
    {
        //Culling
        ScriptableCullingParameters scriptableCullingParameters;
        //Culling may be failed
        if (!CullResults.GetCullingParameters(camera, out scriptableCullingParameters))
            return;

#if UNITY_EDITOR
        //Add UI
        if (camera.cameraType == CameraType.SceneView)
        {
            ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
        }
#endif

        CullResults.Cull(ref scriptableCullingParameters, context, ref cull);

        RenderShadows(context);

        //Setup camera
        context.SetupCameraProperties(camera);

        if (cull.visibleLights.Count > 0)
        {
            //ConfigureLights
            ConfigureLights();
        }

        //command buffer
        CameraClearFlags clearFlags = camera.clearFlags;
        cameraBuffer.BeginSample("ClearRenderTarget");

        cameraBuffer.ClearRenderTarget(
            (clearFlags & CameraClearFlags.Depth) != 0,
            (clearFlags & CameraClearFlags.Color) != 0,
            camera.backgroundColor);
        context.ExecuteCommandBuffer(cameraBuffer);
        cameraBuffer.Clear();

        //Pass light data
        cameraBuffer.SetGlobalVectorArray(visibleColorsID, visibleLightColors);
        cameraBuffer.SetGlobalVectorArray(visibleDirectionsOrPositionsID, visibleLightDirectionOrPositions);
        cameraBuffer.SetGlobalVectorArray(visibleLightAttenuationsID, visibleLightAttenuations);
        cameraBuffer.SetGlobalVectorArray(visibleSpotLightDirectionID, visibleSpotLightDirection);
        if (cull.visibleLights.Count <= 0)
        {
            cameraBuffer.SetGlobalVector(lightIndicesOffsetAndCount, Vector4.zero);
        }
        context.ExecuteCommandBuffer(cameraBuffer);
        cameraBuffer.Clear();

        //Render opaque objects
        var drawSetting = new DrawRendererSettings(camera, new ShaderPassName("SRPDefaultUnlit"));
        drawSetting.sorting.flags = SortFlags.CommonOpaque;
        drawSetting.flags = drawFlags;
        if (cull.visibleLights.Count > 0)
        {
            drawSetting.rendererConfiguration = RendererConfiguration.PerObjectLightIndices8;//，每个对象支持八盏光源的影响
        }
        var filterSetting = new FilterRenderersSettings(true)
        {
            renderQueueRange = RenderQueueRange.opaque,
        };
        context.DrawRenderers(cull.visibleRenderers, ref drawSetting, filterSetting);

        //Draw skybox
        context.DrawSkybox(camera);

        //Draw transparent
        filterSetting.renderQueueRange = RenderQueueRange.transparent;
        drawSetting.sorting.flags = SortFlags.CommonTransparent;
        context.DrawRenderers(cull.visibleRenderers, ref drawSetting, filterSetting);

        //Draw Error
        DrawErrorPipeline(context, camera);

        cameraBuffer.EndSample("ClearRenderTarget");
        context.ExecuteCommandBuffer(cameraBuffer);
        cameraBuffer.Clear();

        context.Submit();

        if (shadowMap)
        {
            RenderTexture.ReleaseTemporary(shadowMap);
            shadowMap = null;
        }
    }

    [Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
    private void DrawErrorPipeline(ScriptableRenderContext context, Camera camera)
    {
        if (null == errorMaterial)
        {
            Shader errorShader = Shader.Find("Hidden/InternalErrorShader");
            errorMaterial = new Material(errorShader)
            {
                hideFlags = HideFlags.HideAndDontSave
            };
        }
        var drawSetting = new DrawRendererSettings(
            camera, new ShaderPassName("ForwardBase"));
        drawSetting.SetShaderPassName(1, new ShaderPassName("PrepassBase"));
        drawSetting.SetShaderPassName(2, new ShaderPassName("Always"));
        drawSetting.SetShaderPassName(3, new ShaderPassName("Vertex"));
        drawSetting.SetShaderPassName(4, new ShaderPassName("VertexLMRGBM"));
        drawSetting.SetShaderPassName(5, new ShaderPassName("VertexLM"));
        drawSetting.SetOverrideMaterial(errorMaterial, 0);
        var filterSetting = new FilterRenderersSettings(true);
        context.DrawRenderers(cull.visibleRenderers, ref drawSetting, filterSetting);
    }

    private void ConfigureLights()
    {
        for (int lightIndex = 0; lightIndex < cull.visibleLights.Count; lightIndex++)
        {
            if (lightIndex == maxVisibleLights)
                break;
            var light = cull.visibleLights[lightIndex];
            visibleLightColors[lightIndex] = light.finalColor;
            Vector4 lightAttenuation = Vector4.zero;
            lightAttenuation.w = 1.0f;
            if (light.lightType == LightType.Directional)
            {
                Vector4 lightDirection = light.localToWorld.GetColumn(2);
                lightDirection.x = -lightDirection.x;
                lightDirection.y = -lightDirection.y;
                lightDirection.z = -lightDirection.z;
                lightDirection.w = 0;
                visibleLightDirectionOrPositions[lightIndex] = lightDirection;
            }
            else
            {
                Vector4 lightPosition = light.localToWorld.GetColumn(3);
                lightPosition.w = 1;
                visibleLightDirectionOrPositions[lightIndex] = lightPosition;
                lightAttenuation.x = 1f / Mathf.Max(light.range * light.range, 0.00001f);
                if (light.lightType == LightType.Spot)
                {
                    Vector4 lightDirection = light.localToWorld.GetColumn(2);
                    lightDirection.x = -lightDirection.x;
                    lightDirection.y = -lightDirection.y;
                    lightDirection.z = -lightDirection.z;
                    visibleSpotLightDirection[lightIndex] = lightDirection;
                    float outerRad = Mathf.Deg2Rad * 0.5f * light.spotAngle;
                    float outerCos = Mathf.Cos(outerRad);
                    float outerTan = Mathf.Tan(outerRad);
                    float innerCos =
                        Mathf.Cos(Mathf.Atan(((64f - 18f) / 64f) * outerTan));
                    float angleRange = Mathf.Max(innerCos - outerCos, 0.001f);
                    lightAttenuation.z = 1f / angleRange;
                    lightAttenuation.w = -outerCos * lightAttenuation.z;
                }
            }
            visibleLightAttenuations[lightIndex] = lightAttenuation;
        }
        //当灯光数量超过最大数量限制
        if (cull.visibleLights.Count > maxVisibleLights)
        {
            int[] lightIndices = cull.GetLightIndexMap();
            for (int i = maxVisibleLights; i < cull.visibleLights.Count; i++)
            {
                lightIndices[i] = -1;
            }
            cull.SetLightIndexMap(lightIndices);
        }
    }

    private void RenderShadows(ScriptableRenderContext context)
    {
        shadowMap = RenderTexture.GetTemporary(shadowMapSize, shadowMapSize, 16, RenderTextureFormat.Shadowmap);
        shadowMap.filterMode = FilterMode.Bilinear;
        shadowMap.wrapMode = TextureWrapMode.Clamp;

        CoreUtils.SetRenderTarget(shadowBuffer, shadowMap, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, ClearFlag.Depth);

        shadowBuffer.BeginSample("RenderShadow");
        context.ExecuteCommandBuffer(shadowBuffer);
        shadowBuffer.Clear();

        Matrix4x4 viewMatrix, projectionMatrix;
        ShadowSplitData splitData;
        cull.ComputeSpotShadowMatricesAndCullingPrimitives(0, out viewMatrix, out projectionMatrix, out splitData);

        shadowBuffer.SetViewProjectionMatrices(viewMatrix, projectionMatrix);
        shadowBuffer.SetGlobalFloat(shadowBiasID, cull.visibleLights[0].light.shadowBias);
        context.ExecuteCommandBuffer(shadowBuffer);
        shadowBuffer.Clear();

        var shadowSetting = new DrawShadowsSettings(cull, 0);
        context.DrawShadows(ref shadowSetting);

        if (SystemInfo.usesReversedZBuffer)
        {
            projectionMatrix.m20 = -projectionMatrix.m20;
            projectionMatrix.m21 = -projectionMatrix.m21;
            projectionMatrix.m22 = -projectionMatrix.m22;
            projectionMatrix.m23 = -projectionMatrix.m23;
        }
        // clip space is (-1, 1), but depth coordinates are (0, 1)
        // so bake the conversion into our matrix
        var scaleOffset = Matrix4x4.identity;
        scaleOffset.m00 = scaleOffset.m11 = scaleOffset.m22 = 0.5f;
        scaleOffset.m03 = scaleOffset.m13 = scaleOffset.m23 = 0.5f;

        // make shadow map available as global shader property
        Matrix4x4 worldtoShadowMatrix = scaleOffset * (projectionMatrix * viewMatrix);

        shadowBuffer.SetGlobalMatrix(worldToShadowMatrixID, worldtoShadowMatrix);
        shadowBuffer.SetGlobalTexture(shadowMapID, shadowMap);

        shadowBuffer.SetGlobalFloat(shadowStrengthID, cull.visibleLights[0].light.shadowStrength);

        shadowBuffer.EndSample("RenderShadow");
        context.ExecuteCommandBuffer(shadowBuffer);
        shadowBuffer.Clear();
    }
}
